﻿namespace Aeon.Emulator.Video.Modes
{
    /// <summary>
    /// A windowed VESA video mode.
    /// </summary>
    internal abstract class VesaWindowed : VideoMode
    {
        #region Private Constants
        private const uint WindowSize = 65536;
        private const uint WindowGranularity = 65536;
        #endregion

        #region Private Fields
        private unsafe readonly byte* videoRam;
        private uint windowOffset;
        private int firstPixel;
        private int firstScanLine;
        #endregion

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the VesaWindowed class.
        /// </summary>
        /// <param name="width">Width of the display mode in pixels.</param>
        /// <param name="height">Height of the display mode in pixels.</param>
        /// <param name="bpp">Color depth of the display mode in bits.</param>
        /// <param name="planar">Value indicating whether the mode is planar.</param>
        /// <param name="fontHeight">Height of the video mode's font in pixels.</param>
        /// <param name="modeType">Value indicating whether the mode is graphical or text.</param>
        /// <param name="video">Current VideoHandler instance.</param>
        protected VesaWindowed(int width, int height, int bpp, bool planar, int fontHeight, VideoModeType modeType, VideoHandler video)
            : base(width, height, bpp, planar, fontHeight, VideoModeType.Graphics, video)
        {
            unsafe
            {
                this.videoRam = (byte*)video.VideoRam.ToPointer();
            }
        }
        #endregion

        #region Public Properties
        /// <summary>
        /// Gets or sets the window position in granularity units.
        /// </summary>
        public uint WindowPosition
        {
            get { return this.windowOffset / WindowGranularity; }
            set { this.windowOffset = value * WindowGranularity; }
        }
        /// <summary>
        /// Gets the number of bytes between rows of pixels.
        /// </summary>
        public override int Stride
        {
            get { return this.Width; }
        }
        /// <summary>
        /// Gets the number of bytes from the beginning of video memory where the display data starts.
        /// </summary>
        public override int StartOffset
        {
            get { return this.firstScanLine * this.Stride + this.firstPixel; }
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Sets the position of the top-left corner of the display area.
        /// </summary>
        /// <param name="firstPixel">First pixel to display.</param>
        /// <param name="scanLine">First scan line to display.</param>
        public void SetDisplayStart(int firstPixel, int scanLine)
        {
            this.firstScanLine = scanLine;
            this.firstPixel = firstPixel;
        }
        #endregion

        #region Internal Methods
        /// <summary>
        /// Reads a byte from an address in video memory.
        /// </summary>
        /// <param name="offset">Address of byte to read.</param>
        /// <returns>Byte at specified address.</returns>
        internal override byte GetVramByte(uint offset)
        {
            unsafe
            {
                return this.videoRam[(offset + this.windowOffset) % VideoHandler.TotalVramBytes];
            }
        }
        /// <summary>
        /// Writes a byte to an address in video memory.
        /// </summary>
        /// <param name="offset">Address where byte will be written.</param>
        /// <param name="value">Value to write to specified address.</param>
        internal override void SetVramByte(uint offset, byte value)
        {
            unsafe
            {
                this.videoRam[(offset + this.windowOffset) % VideoHandler.TotalVramBytes] = value;
            }
        }
        /// <summary>
        /// Reads a 16-bit word from an address in video memory.
        /// </summary>
        /// <param name="offset">Address of word to read.</param>
        /// <returns>Word at specified address.</returns>
        internal override ushort GetVramWord(uint offset)
        {
            unsafe
            {
                return *(ushort*)(this.videoRam + (offset + this.windowOffset) % VideoHandler.TotalVramBytes);
            }
        }
        /// <summary>
        /// Writes a 16-bit word to an address in video memory.
        /// </summary>
        /// <param name="offset">Address where word will be written.</param>
        /// <param name="value">Value to write to specified address.</param>
        internal override void SetVramWord(uint offset, ushort value)
        {
            unsafe
            {
                *(ushort*)(this.videoRam + (offset + this.windowOffset) % VideoHandler.TotalVramBytes) = value;
            }
        }
        /// <summary>
        /// Reads a 32-bit doubleword from an address in video memory.
        /// </summary>
        /// <param name="offset">Address of doubleword to read.</param>
        /// <returns>Doubleword at specified address.</returns>
        internal override uint GetVramDWord(uint offset)
        {
            unsafe
            {
                return *(uint*)(this.videoRam + (offset + this.windowOffset) % VideoHandler.TotalVramBytes);
            }
        }
        /// <summary>
        /// Writes a 32-bit doubleword to an address in video memory.
        /// </summary>
        /// <param name="offset">Address where doubleword will be written.</param>
        /// <param name="value">Value to write to specified address.</param>
        internal override void SetVramDWord(uint offset, uint value)
        {
            unsafe
            {
                *(uint*)(this.videoRam + (offset + this.windowOffset) % VideoHandler.TotalVramBytes) = value;
            }
        }
        /// <summary>
        /// Writes a character at a position on the screen with the current font.
        /// </summary>
        /// <param name="x">Column of character to write.</param>
        /// <param name="y">Row of character to write.</param>
        /// <param name="index">Index of character to write.</param>
        /// <param name="foreground">Foreground color of character to write.</param>
        /// <param name="background">Background color of character to write.</param>
        internal override void WriteCharacter(int x, int y, int index, byte foreground, byte background)
        {
        }
        #endregion
    }
}
